#!/usr/bin/awk -f 

# LogCop parses the appropriate log file for signs of attempted
# break-ins into the system via restricted or non-existent
# accounts.
#
# Attempted break-ins appear in the logs as one of these patterns:
#
# * Failed password for root from 80.5.146.211 port 47459 ssh2
# * Failed password for illegal user ducky from 80.5.146.211 port 47459 ssh2
# * Illegal user temp from 218.19.164.42
#
# The attacker commonly tries to break in by connecting to the SSH service
# and attempting to log on with a user ID and password, triggering one of the
# previous messages.  Log analysis shows that the attacker will try with 
# a list of hundreds of user IDs an passwords.  The attacker will continue
# until he reaches the end of the list.
#
# LogCop previews the log file and, upon matching a potential break-in
# pattern, issues a packet filter instruction to block further 
# connections from the IP address of origin.  Break-in attempts are
# detected by comparing the user ID provided by the potential attacker
# against the entries in a watch list of voided user IDs.
#
# Log files
# ---------
# The program works by monitoring these system-dependent log files:
#
# Linux:   /var/log/secure
# Darwin:  /var/log/system.log
#
# Requires:
# ---------
# * root access for the user executing the script
# * Linux:  iptables
# * Darwin:  ipfw
# * Configuration file:  /etc/logcop.conf
#
# The configuration file is a list of the non-authorized user IDs
# that will trigger blocking, one user ID per line, as in:
# 
# root
# backups
# mysql
# .
# .
#
#
# Running the script
# ------------------
# Linux RH:   tail -n 1 -F /var/log/secure | logcop &
# Darwin:     tail -n 1 -F /var/log/system.log | logcop &
# Linux MDK:  tail -n 1 -F /var/log/system.log | logcop &
#
# You may also add the script to your rc.d or SystemStartup
# routines.  Please read your operating system documentation for
# further details.
#
# Output
# ------
# LogCop adds notifications to the system log indicating when it
# starts, stops, or bans an IP address.


BEGIN {
  # Initialization:
  CONF_FILE   = "/etc/logcop.conf";
  IPFW        = "/sbin/ipfw";
  IPTABLES    = "/sbin/iptables";
  PASSWD_FILE = "/etc/passwd";

  keywords[0]  = "";
  banList[0]   = "";
  whiteList[0] = "";

  "whoami" | getline user;
  if (user != "root") {
    print "You must run LogCop as root.";
    exit 1;
  }

  if (system("test -r " CONF_FILE)) {
    print CONF_FILE " does not exist or has the wrong permissions."
    exit 2;
  }

  "uname" | getline RUNTIME_ENV;
  close("uname");

  system(sprintf("logger -t LogCop started \\(%s\\)", RUNTIME_ENV));
} # end pre-processing


function buildWatchList() {
  # Add system-only IDs to watch list:
  while ((getline line < PASSWD_FILE) > 0) {
      split(line, fields, ":");
      if (fields[3] < 500)  # uid
        keywords[fields[1]] = fields[1];
      else
        whiteList[fields[1]] = fields[1];
  }
  close(PASSWD_FILE);

  # Add the IDs in the logcop.conf watch list:
  while ((getline aKeyword < CONF_FILE) > 0) {
      split(aKeyword, fields, " ");
      if (fields[1] != "#")
        keywords[fields[1]] = fields[1];
  }
  close(CONF_FILE);
} # buildWatchList


function banAddress(userID, address) {
  if (address in banList)
    return;

  if (userID in whiteList)
    return;

  if (address == "UNKNOWN")
    return;

  if ("Linux" == RUNTIME_ENV) {
    command = sprintf("%s -I INPUT -i eth0 -j DROP -s %s", IPTABLES, address);
    system(command);
  }

  if ("Darwin" == RUNTIME_ENV)
    system(sprintf("%s -q add 2042 deny all from %s to any in", IPFW, address));

  system(sprintf("logger -t LogCop %s attempt - banning: %s", userID, address));

  if (userID != "HTTP-request-no-user")
    banList[address] = address;
} # banAddress


/Failed password for/ {  # Linux and Darwin
  if (NF == 16) {
    userID  = $11;
    address = $13;
  }
  else if (NF = 14) {
    userID  = $9;
    address = $11;
  }

  buildWatchList();
  if (userID in keywords)
      banAddress(userID, address);
}


/Illegal user/ {  # Darwin
  userID  = $8;
  address = $10;

  buildWatchList();
  if (userID in keywords)
    banAddress(userID, address);
}


/Bad protocol version identification/ && !/CONNECT/ { # Linux, sshd
  # Pattern:
  # Bad protocol version identification
  userID  = "Bad protocol";
  address = $12;

  banAddress(userID, address);
}


/Did not receive identification string from/ {
  userID  = "No remote ID string";
  address = $12;
  banAddress(userID, address);
}


/CFNetwork/ && /Safari/ && /HTTP\/1.0/ {
  userID  = "HTTP-request-no-user";
  address = $1;
  banAddress(userID, address);
} # Cocoa CFNetwork attack


END {
  system("logger -t LogCop ended");
} # end post-processing

